package edu.mcw.rgd.clinminer.web;

import com.vaadin.ui.AbsoluteLayout;
import com.vaadin.ui.Accordion;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Label;
import com.vaadin.ui.NativeButton;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.Reindeer;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.vaadin.navigator.Navigator;
import org.vaadin.navigator.Navigator.View;

import com.vaadin.annotations.AutoGenerated;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
import com.vaadin.spring.roo.addon.annotations.RooVaadinEntityManagerView;
import com.vaadin.spring.roo.addon.annotations.RooVaadinEntityView;

import edu.mcw.rgd.clinminer.security.Security;
import edu.mcw.rgd.clinminer.web.main.WelcomeView;
import edu.mcw.rgd.clinminer.web.userrole.OntologyView;
import edu.mcw.rgd.clinminer.web.userrole.ParticipantReportView;
import edu.mcw.rgd.clinminer.web.userrole.SearchView;

/**
 * Entity manager view that by default automatically discovers all entity views
 * (classes with the {@link RooVaadinEntityView} annotation) and lets the user
 * choose one of them to display.
 *
 * An entity view must implement the {@link Navigator.View} interface to be
 * automatically added to the view list. Other views can be registered by
 * calling <code>navigator.addView()</code> and added to the sidebar menu by
 * adding a corresponding {@link SidebarItem} to <code>menuItems</code> and
 * <code>viewList</code>.
 *
 * This class is designed to be compatible with the Vaadin Visual Editor.
 *
 * All methods and fields not marked with the {@link AutoGenerated} annotation
 * can be modifiedsearch freely.
 *
 * If you are planning to use the visual editor, do not touch the methods marked
 * with the {@link AutoGenerated} annotation - they will be removed and
 * re-generated when saving the class from the visual editor. Instead, add any
 * custom code to methods called from the constructor after the initial view
 * construction.
 *
 * If you will not use the Vaadin Visual Editor to edit this class, all the
 * {@link AutoGenerated} annotations can be removed.
 */
@RooVaadinEntityManagerView
@Configurable
public class ClinminerEntityManagerView extends CustomComponent implements
		Navigator.ViewChangeListener {
	private static final long serialVersionUID = -3955614673318255786L;

	private static final Logger log = Logger
			.getLogger(ClinminerApplication.class);

	private HorizontalSplitPanel mainLayout;
	private Navigator navigator;
	private HorizontalSplitPanel sidebar;
	private Panel navBar;
	private CssLayout viewList;

	private final int HIDE_BAR_WIDTH = 10;

	private final List<SidebarItem> menuItems = new ArrayList<SidebarItem>();

	@Value("${application.version}")
	private String version;

	@Autowired
	private transient Security security;

	private Accordion accordion;

	public ClinminerEntityManagerView() {
	}

	/**
	 * First build the main layout and set the composition root to support the
	 * visual editor. Then do any custom initialization.
	 *
	 * The constructor will not be automatically regenerated by the visual
	 * editor, and may be edited by the developer.
	 */
	@PostConstruct
	public void init() {
		setSizeFull();

		buildMainLayout();
		setCompositionRoot(mainLayout);
	}

	/**
	 * Menu item in the sidebar menu.
	 *
	 * Entity views are registered automatically, but other custom menu items
	 * can be registered by adding them to menuItems and viewList.
	 */
	protected abstract class SidebarItem extends AbsoluteLayout {
		protected Button selectButton = new NativeButton();

		/**
		 * Creates a sidebar menu item with a view name.
		 *
		 * The view name is also used as the sidebar menu item caption.
		 */
		protected SidebarItem(final String viewName) {
			this(viewName, viewName);
		}

		/**
		 * Creates a sidebar menu item with a view name.
		 */
		protected SidebarItem(final String viewName, String viewCaption) {
			setHeight("36px");
			setWidth("100%");
			addStyleName("menu-item");
			addComponent(selectButton, "top: 0; left: 0; right: 0;");
			selectButton.setCaption(viewCaption);
			selectButton.addListener(new Button.ClickListener() {
				@Override
				public void buttonClick(ClickEvent event) {
					navigator.navigateTo(viewName);
				}
			});
		}

		/**
		 * Creteas a sidebard menu item without a view
		 */
		protected SidebarItem() {
		}

		/**
		 * Checks if a view belongs to this item.
		 *
		 * @param current
		 *            view to check
		 * @return true if the given view belongs to this menu item
		 */
		public abstract boolean ownsView(Navigator.View current);
	}

	private class EntitySidebarItem extends SidebarItem {
		private final Button newButton = new NativeButton("New");
		private final Class<? extends Navigator.View> viewClass;

		public EntitySidebarItem(final String viewName,
				Class<? extends Navigator.View> viewClass) {
			super(viewName);
			this.viewClass = viewClass;

			newButton.addStyleName("new");
			newButton.setVisible(false);
			newButton.setEnabled(false);
			addComponent(newButton, "top: 8px; right: 9px;");

			newButton.addListener(new Button.ClickListener() {
				@Override
				public void buttonClick(ClickEvent event) {
					navigator.navigateTo(viewName + "/new");
				}
			});
		}

		public Button getNewButton() {
			return newButton;
		}

		@Override
		public boolean ownsView(Navigator.View current) {
			return viewClass.equals(current.getClass());
		}
	}

	private class CustomItem extends SidebarItem {
		private static final long serialVersionUID = 1L;
		private final Class<? extends Navigator.View> viewClass;

		public CustomItem(Class<? extends Navigator.View> viewClass,
				String viewName, String caption) {
			super(viewName, caption);
			this.viewClass = viewClass;
		}

		@Override
		public boolean ownsView(View current) {
			return viewClass.equals(current.getClass());
		}

	}

	@Override
	public void navigatorViewChange(Navigator.View previous,
			Navigator.View current) {
		for (SidebarItem item : menuItems) {
			boolean canCreate = false;
			if (current != null && item.ownsView(current)) {
				item.addStyleName("open");
				if (current instanceof AbstractEntityView) {
					canCreate = ((AbstractEntityView) current)
							.isCreateAllowed();
				}
				openContainingMenuTab(item);
			} else {
				item.removeStyleName("open");
			}
			if (item instanceof EntitySidebarItem) {
				EntitySidebarItem entityMenuItem = (EntitySidebarItem) item;
				entityMenuItem.getNewButton().setEnabled(canCreate);
				entityMenuItem.getNewButton().setVisible(canCreate);
				// add alt+n shortcut to activ button
				if (canCreate) {
					entityMenuItem.getNewButton().setClickShortcut(KeyCode.N,
							ModifierKey.ALT);
					entityMenuItem.getNewButton().setDescription(
							"ALT + N to create new");
				} else {
					entityMenuItem.getNewButton().removeClickShortcut();
				}
			}
		}
	}

	private void openContainingMenuTab(Component selectedItem) {
		Iterator<Component> i = accordion.getComponentIterator();
		while (i.hasNext()) {
			CssLayout layout = (CssLayout) i.next();
			Iterator<Component> ii = layout.getComponentIterator();
			while (ii.hasNext()) {
				if (selectedItem == ii.next()) {
					accordion.setSelectedTab(accordion.getTab(layout));
				}
			}
		}
	}

	/**
	 * List all the entity views (classes annotated with
	 * {@link RooVaadinEntityView}) and add them to the list on the left as
	 * buttons. Clicking on one will open a new instance of that entity view in
	 * the main view area.
	 */
	private void addEntityViewsToList() {
		final Map<String, Class> entityViews = listEntityViews();
		for (final String key : entityViews.keySet()) {
			Class viewClass = entityViews.get(key);
			if (Navigator.View.class.isAssignableFrom(viewClass)) {
				navigator.addView(key, viewClass);

				EntitySidebarItem menuItem = new EntitySidebarItem(key,
						viewClass);
				menuItems.add(menuItem);
				viewList.addComponent(menuItem);
			}
		}
	}

	@AutoGenerated
	private HorizontalSplitPanel buildMainLayout() {
		// common part: create layout
		mainLayout = new HorizontalSplitPanel();
		mainLayout.setImmediate(false);
		mainLayout.setWidth("100%");
		mainLayout.setHeight("100%");
		mainLayout.setMargin(false);

		mainLayout.addStyleName("main");
		mainLayout.addStyleName(Reindeer.SPLITPANEL_SMALL);
		mainLayout.setSplitPosition(17);
		mainLayout.setMinSplitPosition(HIDE_BAR_WIDTH, UNITS_PIXELS);

		// navigator
		navigator = new Navigator();
		navigator.setImmediate(false);
		navigator.setWidth("100.0%");
		navigator.setHeight("100.0%");
		navigator.addView("welcome", WelcomeView.class);
		navigator.setMainView("welcome");
		navigator.addListener(this);

		// sidebar
		sidebar = buildSidebar();

		sidebar.addStyleName(Reindeer.LAYOUT_BLUE);
		navBar.addStyleName(Reindeer.PANEL_LIGHT);

		// Order is important LEFT vs. RIGHT
		mainLayout.addComponent(sidebar);
		mainLayout.addComponent(navigator);

		return mainLayout;
	}

	@AutoGenerated
	private HorizontalSplitPanel buildSidebar() {
		// common part: create layout
		sidebar = new HorizontalSplitPanel();
		sidebar.addStyleName("sidebar");
		sidebar.addStyleName(Reindeer.SPLITPANEL_SMALL);
		sidebar.setSplitPosition(HIDE_BAR_WIDTH, UNITS_PIXELS);
		sidebar.setImmediate(false);
		sidebar.setWidth("100.0%");
		sidebar.setHeight("100.0%");
		sidebar.setMargin(false);
		sidebar.setLocked(true);

		// hideBar is a 5px column on the left used to hide the whole sidebar on
		// click
		HorizontalLayout hideBar = new HorizontalLayout();
		hideBar.addStyleName("hidebar");
		hideBar.setSizeFull();
		hideBar.setMargin(false);
		hideBar.addListener(new LayoutClickListener() {
			@Override
			public void layoutClick(LayoutClickEvent event) {

				if (event.getButton() != LayoutClickEvent.BUTTON_LEFT) {
					return;
				}
				if (mainLayout.getSplitPosition() == 17) {
					mainLayout.setSplitPosition(HIDE_BAR_WIDTH, UNITS_PIXELS);
				} else {
					mainLayout.setSplitPosition(17, UNITS_PERCENTAGE);
				}
			}
		});
		sidebar.addComponent(hideBar);

		// menuBar holds both the topBar (appName, userName) and navBar
		VerticalLayout menuBar = new VerticalLayout();
		menuBar.setSizeFull();
		menuBar.setMargin(false);
		sidebar.addComponent(menuBar);

		// top
		VerticalLayout top = buildTopBar();
		menuBar.addComponent(top);

		// navBar
		navBar = buildNavBar();
		menuBar.addComponent(navBar);
		menuBar.setExpandRatio(navBar, 1);

		return sidebar;
	}

	private VerticalLayout buildTopBar() {
		VerticalLayout layout = new VerticalLayout();
		layout.setImmediate(false);
		layout.addStyleName("menu-top");
		layout.setDescription("click to hide sidebar");
		layout.setHeight("-1px");
		layout.setMargin(false);

		// Title label
		Label appName = new Label();
		appName.setImmediate(false);
		appName.setValue("ClinMynEHR v" + version);

		appName.addStyleName(Reindeer.LABEL_H1);
		layout.addComponent(appName);

		// Username and logout
		Component cUsername = buildUsername();
		layout.addComponent(cUsername);

		// spacer
		Label spacer = new Label("");
		spacer.setHeight("2em");
		layout.addComponent(spacer);

		return layout;
	}

	private Component buildUsername() {
		HorizontalLayout hlayout = new HorizontalLayout();

		// labels
		Label welcomeLabel = new Label();
		Label lRole = new Label();

		// button
		NativeButton bLogout = new NativeButton();
		bLogout.setStyleName("logout-link");
		bLogout.setDescription("click to logout");
		bLogout.addListener(new ClickListener() {

			@Override
			public void buttonClick(ClickEvent event) {
				SecurityContextHolder.clearContext();
				getApplication().close();

			}
		});

		if (security != null && security.isAuthenticated()) {
			welcomeLabel = new Label("Welcome&nbsp;", Label.CONTENT_XHTML);
			bLogout.setCaption(security.getUsername());
			lRole = new Label("&nbsp;[" + security.getHighestRole() + "]!",
					Label.CONTENT_XHTML);

			welcomeLabel.addStyleName(Reindeer.LABEL_H2);
			lRole.addStyleName(Reindeer.LABEL_H2);
			hlayout.addComponent(welcomeLabel);
			hlayout.addComponent(bLogout);
			hlayout.addComponent(lRole);

		} else {
			hlayout.addComponent(welcomeLabel);
			welcomeLabel.setCaption("");
		}

		return hlayout;
	}

	@AutoGenerated
	private Panel buildNavBar() {
		// common part: create layout
		navBar = new Panel();
		navBar.setImmediate(false);
		navBar.setWidth("100.0%");
		navBar.setHeight("100.0%");

		accordion = new Accordion();
		accordion.setWidth("100.0%");
		accordion.setHeight("-1px");
		accordion.addStyleName("accordion-menu");

		// Set up tabs
		viewList = new CssLayout();
		viewList.addStyleName("view-list");
		viewList.setImmediate(false);
		viewList.setWidth("100.0%");
		viewList.setHeight("-1px");
		viewList.setMargin(false);

		CssLayout userTab = new CssLayout();
		CssLayout adminTab = new CssLayout();

		accordion.addTab(userTab, "User".toUpperCase());
		accordion.addTab(viewList, "Curator".toUpperCase());
		accordion.addTab(adminTab, "Admin".toUpperCase());

		// add entity views to the list
		addEntityViewsToList();

		SidebarItem itemUserOntology = new CustomItem(OntologyView.class,
				"ontology", "Ontology Browser");
		navigator.addView("ontology", OntologyView.class);
		menuItems.add(itemUserOntology);
		userTab.addComponent(itemUserOntology);

		SidebarItem itemUserAnnotation = new CustomItem(SearchView.class,
				"search", "Search");
		navigator.addView("search", SearchView.class);
		menuItems.add(itemUserAnnotation);
		userTab.addComponent(itemUserAnnotation);

		SidebarItem itemUserTimeline = new CustomItem(
				ParticipantReportView.class, "ParticipantReport",
				"Participant Report");
		navigator.addView("ParticipantReport", ParticipantReportView.class);
		menuItems.add(itemUserTimeline);
		userTab.addComponent(itemUserTimeline);

		navBar.setContent(accordion);

		return navBar;
	}

}